#include <cppscript>


var remove_file_extension(var cpp_filename)
{
	var exe_filename = +cpp_filename;

	var extension = string_find_last(exe_filename, ".");

	if(extension!=null) 
		exe_filename.resize(extension);
	else
		throw exception("not_found", "File extension not found");

	return exe_filename;
}


var is_cpp_file(var cpp_filename)
{
	return substring( cpp_filename, range(-4,-1) ) == ".cpp";
}


void validate_args(var script_runner)
{
	if(script_runner["args"].size()==0)
	{
		throw exception(
			"usage", "Usage: cppscript [-v] <cpp_file> [cpp_file...] [-a] [args...]\n"
			"    C++Script " + version() + " Copyright (C) 2008 Calum Grant\n"
			"    This program comes with ABSOLUTELY NO WARRANTY; This is free software,\n"
			"    and you are welcome to redistribute it under certain conditions;\n"
			"    See http://calumgrant.net/cppscript for details."
			);
	}
}


void parse_args(var script_runner)
{
	validate_args(script_runner);

	script_runner["sources"] = array();
	script_runner["program_args"] = array();
	script_runner["verbose"] = false;
	var program_args = false;

	foreach(i, script_runner["args"])
	{
		if( program_args )
			script_runner["program_args"].push_back(i);
		else if( i == "-a" )
			program_args = true;
		else if( i == "-v" )
			script_runner["verbose"] = true;
		else if( is_cpp_file( i ) )
			script_runner["sources"].push_back(i);
		else
		{
			script_runner["program_args"].push_back(i);
			program_args = true;
		}
	}

	if( script_runner["verbose"] )
		writeln( "C++Script version " + version() );

	if( script_runner["sources"].empty() )
		throw exception("no_sources", "No source files specified");

	script_runner["primary_source_file"] = script_runner["sources"].front();
}


void run(var script_runner)
{
	exec(script_runner["exe_filename"], script_runner["program_args"]);
}


void write_config(var script_runner)
{
	pickle_file("cppscript.config", script_runner["config"]);
}


var gcc_probe(var compiler)
{
	return system("g++ -v") == 0;
}


void gcc_compile(var compiler, var cpp_file)
{
	var obj = remove_file_extension(cpp_file) + ".o";
	if( system( "g++ -c " + cpp_file + " -o " + obj) )
		throw exception("compile", "Compilation failed");
}


void gcc_link(var compiler, var exe_name, var cpp_list)
{
	var objs="";
	foreach( src, cpp_list )
	{
		var obj = remove_file_extension(src) + ".o";
		objs += " ",
		objs += obj;
	}

	if( system( "g++ -o " + exe_name + objs + " -ldynamic") )
		throw exception("link", "Linking failed");
}


var gcc()
{
	var compiler = object("gcc_compiler");
		compiler["probe"] = gcc_probe;
		compiler["compile"] = gcc_compile;
		compiler["link"] = gcc_link;
		compiler["name"] = "gcc";
	return compiler;
}


var msvc_probe( var compiler )
{
	return system("cl") == 0;
}


void msvc_compile( var compiler, var cpp )
{
	var obj = remove_file_extension(cpp) + ".obj";

	/// \todo Pass compiler flags and libs to the cppscript command line
	if( system( "cl /c " + cpp + " /EHsc /nologo /O2 /Oi /GL /MD /Gy /Zi /TP /Fo" + obj ) )
		throw exception("compile", "Compilation failed");
}


void msvc_link( var compiler, var exe, var cpp_list )
{
	var objs="";
	foreach( src, cpp_list )
	{
		var obj = remove_file_extension(src) + ".obj";
		objs += " ",
		objs += obj;
	}

	if( system("link /out:" + exe + objs + " /nologo /LTCG /OPT:REF /OPT:ICF /DYNAMICBASE /NXCOMPAT libscript_main.lib libcppscript.lib") )
		throw exception("link", "Linking failed");
}


var msvc()
{
	var compiler = object("msvc_compiler");
		compiler["probe"] = msvc_probe;
		compiler["compile"] = msvc_compile;
		compiler["link"] = msvc_link;
		compiler["name"] = "msvc";
	return compiler;
}


void deduce_config(var script_runner)
{
#if defined(_MSC_VER)
	script_runner["config"] = script_runner["compilers"]["msvc"];
#elif defined(__GNUC__)
	script_runner["config"] = script_runner["compilers"]["gcc"];
#else
	#error Unsupported compiler
#endif
}


void read_config(var script_runner)
{
	// Used to read a config file, and probe for the compiler on the platform.
	// This isn't done any more.

	deduce_config(script_runner);
}


void find_exe(var script_runner)
{
	var exe =  remove_file_extension(script_runner["primary_source_file"]);
	script_runner["exe_filename"] = exe;

	var stat = fstat( exe );

	if( stat ) 
	{
		script_runner["exe_date"] = stat["modified"];
	}
	else
	{
		stat = fstat( exe + ".exe" );
		if( stat )
		{
			script_runner["exe_date"] = stat["modified"];
		}
	}
}


void compile(var script_runner)
{
	var files_out_of_date = array();

	foreach(src, script_runner["sources"])
	{
		var stat = fstat(src);
		if( !stat )
			throw exception("compile_error", "Source file " + src + " not found");
		if( !script_runner.contains("exe_date") || stat["modified"] > script_runner["exe_date"] )
		{
			files_out_of_date.push_back(src);
		}
	}

	if( files_out_of_date )
	{
		read_config(script_runner);
	}

	foreach(src, files_out_of_date)
	{
		if( script_runner["verbose"] ) writeln( "Compiling " + src );
		script_runner["config"]["compile"](src);
	}

	if( files_out_of_date )
	{
		script_runner["config"]["link"](
			script_runner["exe_filename"], 
			script_runner["sources"]);
	}
}


var create_script_runner(var args)
{
	var script_runner = object();
		script_runner["args"] = args;
		script_runner["sources"] = array();
		script_runner["program_args"] = array();
		script_runner["compilers"] = 
			map	("gcc", gcc())
				("msvc", msvc());

	return script_runner;
}


var script_main(var args)
{
	try
	{
		var script_runner = create_script_runner(args);
		validate_args(script_runner);
		parse_args(script_runner);
		find_exe(script_runner);
		compile(script_runner);
		run(script_runner);
		return 0;
	}
	catch(var v)
	{
		writeln( v["text"] );
		return 1;
	}
}
